package com.miet.mp45.filippov.crypto.util;

import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.cert.X509Certificate;

import javax.security.auth.x500.X500Principal;

import org.apache.log4j.Logger;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.asn1.x500.X500Name;


public class CryptoUtil
{
    
    private final static String ENCRYPTION_ALGORITHM = "RSA";
    private final static String SIGNATURE_ALGORITHM = "MD5withRSA";
    private final static Logger logger = Logger.getLogger(CryptoUtil.class);
    
    public static KeyPair generateKeyPair(int keySize)
    {
        KeyPairGenerator generator = null;
        try
        {
            generator = KeyPairGenerator.getInstance(ENCRYPTION_ALGORITHM);
            generator.initialize(keySize);
        }
        catch (NoSuchAlgorithmException e)
        {
            logger.error("Oops! This error should not appear! But: " + e);
        }
        
        return generator.genKeyPair();
    }
    
    public static KeyPair generateKeyPair(byte[] encodedPrivateKey) throws InvalidKeySpecException
    {
        KeyPair keyPair = null;
        
        try
        {
            KeyFactory factory = KeyFactory.getInstance(ENCRYPTION_ALGORITHM);
            PublicKey publicKey = factory.generatePublic(new X509EncodedKeySpec(extractPublicKey(encodedPrivateKey)));
            PrivateKey privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKey));
            keyPair = new KeyPair(publicKey, privateKey);
        }
        catch (NoSuchAlgorithmException exc)
        {
            logger.error("Oops! This error should not appear! But: " + exc);
        }
        
        return keyPair;
    }
    
    public static byte[] extractPublicKey(byte[] encodedPrivateKey)
    {
        byte[] encodedPublicKey = null;
        
        try
        {
            KeyFactory factory = KeyFactory.getInstance(ENCRYPTION_ALGORITHM);
            PrivateKey privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKey));
            RSAPrivateCrtKeySpec privateKeySpec = factory.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
            RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(privateKeySpec.getModulus(),
                                                                  privateKeySpec.getPublicExponent());
            encodedPublicKey = factory.generatePublic(publicKeySpec).getEncoded();
        }
        catch (Exception e)
        {
            logger.error("Oops! This error should not appear! But: " + e);
        }
        
        return encodedPublicKey;
    }
    
    public static byte[] sign(byte[] data, byte[] encodedPrivateKey)
    {
        byte[] signedData = null;
        try
        {
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(KeyFactory.getInstance(ENCRYPTION_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(encodedPrivateKey)));
            signature.update(data);
            signedData = signature.sign();
        }
        catch (Exception e)
        {
            logger.error("Oops! This error should not appear! But: " + e);
        }
        
        return signedData;
    }
    
    public static byte[] generateCSR(String commonName,
                                     String orgnizationUnit,
                                     String organizationName,
                                     String countryName,
                                     byte[] encodedPrivateKey) throws OperatorCreationException,
                                                                      IOException,
                                                                      InvalidKeySpecException
    {
        PKCS10CertificationRequest certificationRequest = null;
        
        StringBuilder subjectInfoBuilder = new StringBuilder();
        subjectInfoBuilder.append("CN=").append(commonName).append(",")
                          .append("OU=").append(orgnizationUnit).append(",")
                          .append("O=").append(organizationName).append(",")
                          .append("C=").append(countryName);
        logger.info(subjectInfoBuilder);
                                    
        X500Principal subject = new X500Principal(subjectInfoBuilder.toString());
        KeyPair keyPair = generateKeyPair(encodedPrivateKey);
        
        JcaPKCS10CertificationRequestBuilder certificationRequestBuilder = 
            new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic());
        ContentSigner signer = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(keyPair.getPrivate());
        certificationRequest = certificationRequestBuilder.build(signer);
        
        return certificationRequest.getEncoded();
    }
    
    public static byte[] generateCertificate(byte[] encodedCSR,
                                             String commonName,
                                             String orgnizationUnit,
                                             String organizationName,
                                             String countryName,
                                             byte[] encodedPrivateKey) throws Exception
    {
        PKCS10CertificationRequest csr = new PKCS10CertificationRequest(encodedCSR);
        
        StringBuilder issuerInfoBuilder = new StringBuilder();
        issuerInfoBuilder.append("CN=").append(commonName).append(",")
                         .append("OU=").append(orgnizationUnit).append(",")
                         .append("O=").append(organizationName).append(",")
                         .append("C=").append(countryName);
        X500Name issuer = new X500Name(issuerInfoBuilder.toString());
        
        KeyPair keyPair = generateKeyPair(encodedPrivateKey);
        
        X509Certificate[] certChain = null;
        try
        {
            certChain = new CSRSigner(issuer, keyPair, SIGNATURE_ALGORITHM).sign(csr);
        }
        catch (OperatorCreationException exc)
        {
            logger.error("Oops! This error should not appear! But: " + exc);
        }
        
        logger.info("Generated X.509 certificate:\n" + certChain[0]);
        return certChain[0].getEncoded();
    }
    
//    private static String getHexString(byte[] array)
//    {
//        StringBuilder resultBuilder = new StringBuilder(2*array.length);
//        for (byte b : array)
//        {
//            resultBuilder.append(Integer.toHexString((b & 0xff) + 0x100).substring(1));
//        }
//        
//        return resultBuilder.toString();
//    }
    
//    public static void main(String[] args) throws OperatorCreationException, IOException
//    {
//        KeyPair keyPair = generateKeyPair(2048);
//        logger.info(getHexString(keyPair.getPrivate().getEncoded()));
//        logger.info(getHexString(keyPair.getPublic().getEncoded()));
////        logger.info(cryptoHelper.getHexString(cryptoHelper.getPublicKey(keyPair.getPrivate().getEncoded()).getEncoded()));
////        byte[] csr = generateCSR("Test Name", "Test Unit", "Test Org", "Test Country", keyPair);
////        logger.info(new String(csr));
//    }

}
